#include <stdio.h>

int main() {
    //1、在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来,这些"按序排列(地址连续)"的同类数据元素的集合称为数组
    //2、在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。
    // 因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。
    //3、总结:数组中的元素是连续的（元素的内存地址连续）  同一数组所有的成员都是相同的数据类型

    printf("5.1一维数组展示:\n");
    //语法: 数据类型 数组名[常量表达式] TIPS:数组名命名规范遵守"标识符的命名规范"，常量表达式是数组长度-数组中可存储元素的个数
    int arr[10];//定义了一个数组，名称是arr，可以存储10个元素，每个元素都是int类型
    //使用:
    //通过"下标"来访问数组中的元素。下标的范围是0——数组长度-1。
    //TIPS:如果数组不初始化，则内容为随机值(原因:在内存中随机一片区域划分出来提供给数组使用，则这片区域本来存储的的二进制是什么则就是什么值)
    printf("初始化前一维数组的所有元素值:");
    //遍历数组，并输出每个成员的值
    for (int i = 0; i < 10; ++i) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    for (int i = 0; i < 10; ++i) {
        arr[i] = i;//给数组中的每个元素赋值
    }
    printf("赋值后一维数组的所有元素值:");
    //遍历数组，并输出每个成员的值
    for (int i = 0; i < 10; ++i) {
        printf("%d ", arr[i]);
    }
    //WARN:如果下标超过数组长度，则程序或许可以编译通过，但是结果是未知的！(原因与不初始化直接使用相同)
    printf("\n");

    printf("使用超过数组长度的下标访问一维数组:%d\n", arr[10]);
    //技巧:我们可以通过数组中元素的数据类型反推出数组的长度
    printf("一维数组在内存中占用的字节数:%d\n", sizeof(arr));
    printf("一维数组的长度:%d\n", sizeof(arr) / sizeof(arr[0]));

    //数组的初始化:
    //除了通过遍历对数组元素逐个赋值之外，还可以采用初始化赋值和动态赋值的方式
    int array1[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//定义一个数组，同时初始化所有成员变量
    printf("初始化赋值的一维数组的所有元素值:");
    for (int i = 0; i < 10; ++i) {
        printf("%d ", array1[i]);
    }
    printf("\n");

    int array2[10] = {1, 2, 3};//初始化前三个成员，后面所有元素都设置为0
    printf("初始化赋值的一维数组的所有元素值:");
    for (int i = 0; i < 10; ++i) {
        printf("%d ", array2[i]);
    }
    printf("\n");

    int array3[10] = {0};//所有的成员都设置为0
    printf("初始化赋值的一维数组的所有元素值:");
    for (int i = 0; i < 10; ++i) {
        printf("%d ", array3[i]);
    }
    printf("\n");

    //[]可以不定义元素个数，但定义时就必须初始化
    int array4[] = {1, 2, 3, 4, 5};//定义了一个数组，它包含有5个元素
    printf("初始化赋值的一维数组的所有元素值:");
    for (int i = 0; i < 5; ++i) {
        printf("%d ", array4[i]);
    }
    printf("\n");

    //一维数组元素在内存中的存储:
    //之前也说过数组是"地址连续"的同类数据元素的集合，要证明这一点只需要将数组中每个元素的地址输出出来就可以证明了
    printf("一维数组元素在内存中的存储:");
    for (int j = 0; j < 5; ++j) {
        //从输出结果我们也可以非常明显的看到，连续地址由低地址->高地址，且元素与元素之间间隔着它们在内存中占用的字节数
        printf("%p ", &array4[j]);//目前只需要理解通过&可以输出对应值在内存空间的首地址即可
    }
    printf("\n");

    //定义一维数组时提供的元素个数超过数组容量时，或许可以编译成功并运行(根据编译器而定)，但是多余的元素不会产生越界溢出覆盖
    //编译警告: warning: excess elements in array initializer
    int array5[2] = {1, 2, 90};
    printf("初始化赋值(但元素个数多了)的一维数组的所有元素值:");
    for (int j = 0; j < 3; ++j) {
        //从输出结果我们也可以非常明显的看到，连续地址由低地址->高地址，且元素与元素之间间隔着它们在内存中占用的字节数
        printf("%d ", array5[j]);//目前只需要理解通过&可以输出对应值在内存空间的首地址即可
    }
    printf("\n");

    //但是若是通过赋值，且赋值时数组下标出现了越界，则可能会出现覆盖掉之前定义数组(或者其他数据类型)存储的值的情况
    array5[2] = 99;//初始化容量是2，则下标范围应该是0-1，所以2就是越界了一位。
    printf("越界溢出后之前的数组首地址存储的值被修改:%d\n", array4[0]);
    //可以发现两个数组由于内存的分配的原因，刚刚好相差了2个int类型也就是8个字节的，所以数组的越界刚好修改到了之前的首地址。
    printf("实际原因是内存分配的缘故:%p %p %p\n", array5, &array5[1], array4);


    printf("5.2二维数组展示:\n");
    //语法: 数据类型 数组名[常量表达式1][常量表达式2]
    //我们可以将二维数组当作一个有行有列的二维矩阵，表达式1代表行，表达式2代表列，二维数组可以理解为由"表达式1"个一维数组组成的数组
    int matrix[3][4];//定义了一个3行4列二维数组，名称是matrix，每个元素都是int类型
    //使用:
    //同样通过"下标"来访问数组中的元素，但是需要用到两个下标。行下标的范围是0——"常量表达式1"-1，列下标的范围是0——"常量表达式2"-1。然后通过双重循环访问。
    printf("一行一行访问二维数组中的元素:\n");
    for (int k = 0; k < 3; ++k) {
        for (int i = 0; i < 4; ++i) {
            printf("%d ", matrix[k][i]);
        }
        printf("\n");
    }
    printf("一列一列访问二维数组中的元素:\n");
    for (int k = 0; k < 4; ++k) {
        for (int i = 0; i < 3; ++i) {
            printf("%d ", matrix[i][k]);
        }
        printf("\n");
    }
    //初始化(不初始化的情况下数组内元素随机，原理同一维数组):
    int matrix1[3][4] = {
            {1, 2,  3,  4},
            {5, 6,  7,  8},
            {9, 10, 11, 12},
    };
    printf("通过分段赋值初始化二维数组:\n");
    for (int k = 0; k < 3; ++k) {
        for (int i = 0; i < 4; ++i) {
            printf("%d ", matrix1[k][i]);
        }
        printf("\n");
    }
    int matrix2[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    printf("通过连续赋值初始化二维数组:\n");
    for (int k = 0; k < 3; ++k) {
        for (int i = 0; i < 4; ++i) {
            printf("%d ", matrix2[k][i]);
        }
        printf("\n");
    }
    int matrix3[3][4] = {1, 2};
    printf("通过部分赋值初始化二维数组(为初始化则为0):\n");
    for (int k = 0; k < 3; ++k) {
        for (int i = 0; i < 4; ++i) {
            printf("%d ", matrix3[k][i]);
        }
        printf("\n");
    }
    int matrix4[3][4] = {0};
    printf("通过0值初始化二维数组(所有元素均设置为0):\n");
    for (int k = 0; k < 3; ++k) {
        for (int i = 0; i < 4; ++i) {
            printf("%d ", matrix4[k][i]);
        }
        printf("\n");
    }
    int matrix5[][4] = {1, 2, 3, 4, 5};
    printf("不指定行数初始化二维数组(此时定义时就必须初始化):\n");
    //此时若出现行数的越界，则元素随机
    for (int k = 0; k < 3; ++k) {
        for (int i = 0; i < 4; ++i) {
            printf("%d ", matrix5[k][i]);
        }
        printf("\n");
    }
    //错误的初始化:不能不指定列数
//    int matrix6[4][] = {1, 2, 3, 4, 5};
    //二维数组元素在内存中的存储:
    //TIPS:内存中并不存在二维数组!内存中本质上只存在一维数组，二维数组在硬件实际的存储是连续编址的，即放完一行之后顺次放入第二行，和一维数组存放方式是一样的。
    printf("二维数组元素在内存中的存储:\n");
    for (int k = 0; k < 3; ++k) {
        for (int i = 0; i < 4; ++i) {
            printf("%p ", &matrix4[k][i]);
        }
        printf("\n");
    }
    printf("\n");

    return 0;
}